<?php

namespace mpend\models;

use Yii;
use yii\base\Exception;
use common\models\app\AppToken;
use common\models\app\AppDevice;
use common\models\app\AppClient;
use common\models\app\AppVersion;
use mpend\models\User;
use common\models\user\UserMeta;
use common\models\user\UserConnect;
use common\helpers\ArrayHelper;
use common\helpers\IpHelper;
use common\helpers\UuidHelper;

/**
 * 微信小程序接口访问授权接口
 * AuthWechatForm
 * 
 * @author emhome <emhome@163.com>
 * @since 1.0
 */
class AuthWechatForm extends \common\base\Model {

    /**
     * @var string 应用ID
     */
    public $appid;

    /**
     * @var string 时间戳
     */
    public $timestamp;

    /**
     * @var string 客户端操作系统
     */
    public $platform;

    /**
     * @var string 设备机型
     */
    public $model;

    /**
     * @var integer Build版本号
     */
    public $build;

    /**
     * @var string 小程序发送的code
     */
    public $code;

    /**
     * @var string 随机字符串
     */
    public $nonce;

    /**
     * @var string 签名串
     */
    public $sign;

    /**
     * @var Object 用户信息对象
     */
//    public $userInfo;

    /**
     * @var object 客户端
     */
    private $_client = null;

    /**
     * @var object jscode
     */
    private $_authData = null;

    /**
     * @var array
     */
    private $_userData = [];

    /**
     * @inheritdoc
     */
    public function rules() {
        return [
            [['appid', 'platform', 'build', 'code', 'nonce', 'sign', 'timestamp'], 'required'],
            [['build', 'timestamp'], 'integer'],
            [['model'], 'string', 'max' => 64],
            [['platform', 'nonce'], 'string', 'max' => 32],
            ['appid', 'validateAppId'],
            ['build', 'validateBuild'],
            ['sign', 'validateSignature'],
            ['code', 'validateJscode'],
//            ['userInfo', 'validateUserInfo'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels() {
        return [
            'appid' => 'AppID',
            'code' => '小程序授权CODE',
            'model' => '设备型号',
            'platform' => '客户端平台',
            'build' => 'Build',
            'nonce' => '随机串',
            'timestamp' => '时间戳',
            'sign' => '签名',
        ];
    }

    /**
     * 验证APPID是否为授权应用
     * @param string $attribute `appid`
     */
    public function validateUserInfo($attribute) {
        if ($this->hasErrors()) {
            return;
        }
        $userInfo = $this->$attribute;

        $avatar = ArrayHelper::getValue(Yii::$app->params, ['defaultImage', 'avatar'], '/assets/default/images/avatar.png');
        $nickname = null;
        $gender = 0;
        $location = '';
        if ($userInfo) {
            $nickname = ArrayHelper::getValue($userInfo, 'nickName');
            $avatar = ArrayHelper::getValue($userInfo, 'avatarUrl', $avatar);
            $gender = ArrayHelper::getValue($userInfo, 'gender', 0);
            $location = implode('·', array_filter([
                ArrayHelper::getValue($userInfo, 'country'),
                ArrayHelper::getValue($userInfo, 'province'),
                ArrayHelper::getValue($userInfo, 'city'),
            ]));
        }
        $this->_userData = compact('nickname', 'avatar', 'gender', 'location');
    }

    public function getUserData() {
        return $this->_userData;
    }

    /**
     * 验证APPID是否为授权应用
     * @param string $attribute `appid`
     */
    public function validateAppId($attribute) {
        if (!$this->hasErrors()) {
            $client = $this->getClient();
            if (!$client) {
                $this->addError($attribute, '未授权的应用ID');
            } else {
                $this->_client = $client;
            }
        }
    }

    /**
     * 验证Build版本号是否正确
     * @param string $attribute `build`
     */
    public function validateBuild($attribute) {
        if (!$this->hasErrors()) {
            $version = AppVersion::find()->where([
                'build' => $this->build,
                'os' => AppVersion::OS_MP,
            ])->andWhere(['>', 'status', AppVersion::STATUS_DEFAULT])
            ->exists();
            if (!$version) {
                $this->addError($attribute, '请求的版本已不提供支持！');
            }
        }
    }

    /**
     * 校验请求的签名
     * @param string $attribute `sign`
     */
    public function validateSignature($attribute) {
        if ($this->hasErrors()) {
            return;
        }
        $attributes = $this->attributes;
        Yii::error($attributes);
        unset($attributes['userInfo']);
        $validated = Yii::$app->security->validateSignature($attributes, $this->getClient()->secret);
        if ($validated !== true) {
            $tips = '签名校验失败';
            if (YII_DEBUG) {
                $tips .= $validated;
            }
            $this->addError($attribute, $tips);
        }
    }

    /**
     * 校验请求的签名
     * @param string $attribute `sign`
     */
    public function validateJscode($attribute) {
        if (!$this->hasErrors()) {
            $authData = $this->setAuthData($this->$attribute);
            if (!$authData) {
                $this->addError($attribute, '获取微信Openid失败');
            }
        }
    }

    /**
     * 获取APP授权应用客户端
     * @return \common\models\app\AppClient|null
     */
    protected function getClient() {
        if ($this->_client === null) {
            $this->_client = AppClient::findByAppId($this->appid);
        }
        return $this->_client;
    }

    /**
     * 获取APP授权应用客户端
     * @return \common\models\app\AppClient|null
     */
    protected function getAuthData() {
        return $this->_authData;
    }

    /**
     * 获取APP授权应用客户端
     * @return \common\models\app\AppClient|null
     */
    protected function setAuthData($code = null) {
        if (YII_ENV_DEV || YII_DEBUG) {
            return $this->_authData = [
                'openid' => 'oIJym5CWe1imSAP7jT4P9EABy6z8', //	string 	用户唯一标识
                'session_key' => 'YLwaY9z/Wt/1Myd5YrUnsw==', //	string 	会话密钥
            ];
        }
        if ($code) {
            /* @var $wechat \common\components\wechat\MpWechat */
            $wechat = Yii::$app->wechatMp;
            $this->_authData = $wechat->getJscode2Session($code);
        }
        return $this->_authData;
    }

    /**
     * 获取APP授权应用客户端
     * @return \common\models\app\AppClient|null
     */
    protected function buildSN($openid) {
        $orgs = AppVersion::OS_MP . $this->build . $this->platform . $openid;
        $str = strtoupper(md5($orgs));
        return UuidHelper::format($str);
    }

    /**
     * 认证操作
     * @inheritdoc
     */
    public function authorize() {
        if (!$this->validate()) {
            return $this->dumpError('授权参数校验失败', null, 401);
        }
        $authData = $this->getAuthData();
        $openid = ArrayHelper::getValue($authData, 'openid');
        $unionid = ArrayHelper::getValue($authData, 'unionid');
        if (!$openid) {
            return $this->dumpError('获取微信Openid失败', null, 401);
        }
        //自动注册
        $user = $this->wechatRegister($openid, $unionid);
        //绑定设备
        $sn = $this->buildSN($openid);
        //访问授权token
        $token = $this->authToken($sn, $user->id);
        return compact('openid', 'user', 'token');
    }

    protected function wechatRegister($openid, $unionid) {
        $connect = UserConnect::findOrNew([
            'client' => UserConnect::CLIENT_MP,
            'openid' => $openid,
        ]);
        if ($connect->isNewRecord) {
            $connect->unionid = $unionid;
        }
        if (!$connect->user_id) {
            $user = new User();

            $user->setUsernameByOpenid(UserConnect::CLIENT_MP, $openid);
            $user->generateAuthKey();
            $user->ip = IpHelper::ip();
            $user->avatar = ArrayHelper::getValue(Yii::$app->params, ['defaultImage', 'avatar'], '/assets/default/images/avatar.png');
            $user->status = User::STATUS_DEFAULT;

            $meta = new UserMeta();
            $meta->gender = 0;
            $transaction = Yii::$app->db->beginTransaction();
            try {
                if (!$user->save()) {
                    throw new Exception('账号信息创建失败');
                }
                $meta->id = $user->id;
                if (!$meta->save()) {
                    throw new Exception('用户基本信息保存失败');
                }
                $connect->user_id = $user->id;
                if (!$connect->save()) {
                    throw new Exception('用户基本信息保存失败');
                }
                $transaction->commit();
            } catch (Exception $e) {
                $transaction->rollBack();
                return $this->dumpError($e->getMessage(), null, 401);
            }
        } else {
            if (!$connect->save()) {
                return $this->dumpError('关联信息保存失败:' . $connect->errorMsg, null, 401);
            }
        }
        return $connect->user;
    }

    protected function authToken($sn, $userid = 0) {
        $transaction = Yii::$app->db->beginTransaction();
        try {
            $device = AppDevice::findOrNew([
                'sn' => $sn,
                'os' => $this->platform,
            ]);
            $device->attributes = [
                'build' => $this->build,
                'model' => $this->model,
            ];
            if (!$device->save()) {
                throw new Exception('设备注册失败！' . $device->errorMsg);
            }
            $token = AppToken::findOrNew([
                'client_id' => $this->client->id,
                'device_id' => $device->id,
            ]);
            $token->user_id = $userid;
            $token->generateAccessToken();
            if (!$token->save()) {
                throw new Exception('访问令牌生成失败！' . $token->errorMsg);
            }
            $transaction->commit();
        } catch (Exception $e) {
            $transaction->rollBack();
            return $this->dumpError($e->getMessage(), null, 401);
        }

        $data = [
            'access_token' => $token->access_token,
            'expired_at' => $token->expired_at,
        ];

        if (YII_DEBUG) {
            $data['_debug'] = [
                'encrypt_token' => Yii::$app->security->encryptDataByKey($token->access_token)
            ];
        }
        return $data;
    }

}
